!! Copyright (C) 2009,2010,2011,2012  Marco Restelli
!!
!! This file is part of:
!!   LDGH -- Local Hybridizable Discontinuous Galerkin toolkit
!!
!! LDGH is free software: you can redistribute it and/or modify it
!! under the terms of the GNU General Public License as published by
!! the Free Software Foundation, either version 3 of the License, or
!! (at your option) any later version.
!!
!! LDGH is distributed in the hope that it will be useful, but WITHOUT
!! ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
!! or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
!! License for more details.
!!
!! You should have received a copy of the GNU General Public License
!! along with LDGH. If not, see <http://www.gnu.org/licenses/>.
!!
!! author: Marco Restelli                   <marco.restelli@gmail.com>


!>\brief
!! This module provides a unified interface to the specific test case
!! modules. In this sense, it is similar to \c ADR/mod_testcases.f90,
!! but of course is taylored to the definition of Navier-Stokes
!! problems.
!!
!! \n
!!
!! The specific test case modules, such as \c mod_cavity_test, should
!! never be accessed directly, because this would result in very
!! cumbersome code. A much cleaner solution is using this module as
!! unique interface to all the test case modules, so that when a new
!! test case is added it suffices to change this module.
!! 
!! More precisely, to add a new test case:
!! <ol>
!!
!!  <li> write a new module \c mod_testname whose public interface is
!!   given by:
!!   <ul>
!!    <li>\c mod_testname_constructor:  constructor
!!    <li>\c mod_testname_destructor:   destructor
!!    <li>\c test_name:     name of the test case (\c character)
!!    <li>\c test_description: short description (\c character)
!!    <li>\c coeff_visc:    viscosity coefficient
!!    <li>\c coeff_f:       forcing term
!!    <li>\c coeff_dir:     Dirichlet datum (only used if there are
!!     Dirichlet bcs)
!!    <li>\c coeff_neu:     Neumann datum (only used if there are
!!     Neumann/Robin bcs)
!!    <li>\c coeff_rob:     Robin coefficient (only used if there are
!!     Neumann/Robin bcs)
!!   </ul>
!!   The following functions are optional and are used only if the
!!   errors are computed
!!   <ul>
!!    <li>\c coeff_u:       exact solution, velocity
!!    <li>\c coeff_gradu:   exact solution, velocity gradient
!!    <li>\c coeff_p:       exact solution, pressure
!!    <li>\c coeff_gradp:   exact solution, pressure gradient
!!   </ul>
!!   For time dependent problems, the initial state is specified by
!!   <ul>
!!    <li>\c coeff_init:    initial velocity
!!   </ul>
!! 
!!  <li> use the new module in the present one;
!!
!!  <li> include the proper reference in the \c casename case blocks
!!
!!  <li> add the new module into the Makefile:
!!   <ul>
!!    <li> add the new module in the variable \c OBJ_TEST
!!    <li> add a target for the new module.
!!   </ul>
!!
!! </ol>
!<----------------------------------------------------------------------
module mod_testcases

!-----------------------------------------------------------------------

 use mod_messages, only: &
   mod_messages_initialized, &
   error,   &
   warning, &
   info

 use mod_kinds, only: &
   mod_kinds_initialized, &
   wp

 use mod_cavity_test, only: &
   mod_cavity_test_constructor, &
   mod_cavity_test_destructor,  &
   cavity_test_name  => test_name, &
   cavity_test_description => test_description,&
   cavity_coeff_visc => coeff_visc,&
   cavity_coeff_f    => coeff_f,   &
   cavity_coeff_dir  => coeff_dir, &
   cavity_coeff_init => coeff_init

 use mod_cylinder_test, only: &
   mod_cylinder_test_constructor, &
   mod_cylinder_test_destructor,  &
   cylinder_test_name  => test_name, &
   cylinder_test_description => test_description,&
   cylinder_coeff_visc => coeff_visc,&
   cylinder_coeff_f    => coeff_f,   &
   cylinder_coeff_dir  => coeff_dir, &
   cylinder_coeff_init => coeff_init

 use mod_squeeze_test, only: &
   mod_squeeze_test_constructor, &
   mod_squeeze_test_destructor,  &
   squeeze_test_name  => test_name, &
   squeeze_test_description => test_description,&
   squeeze_coeff_visc => coeff_visc,&
   squeeze_coeff_f    => coeff_f,   &
   squeeze_coeff_dir  => coeff_dir, &
   squeeze_coeff_u    => coeff_u,   &
   squeeze_coeff_gradu=> coeff_gradu,&
   squeeze_coeff_p    => coeff_p,   &
   squeeze_coeff_gradp=> coeff_gradp

 use mod_poiseuille_test, only: &
   mod_poiseuille_test_constructor, &
   mod_poiseuille_test_destructor,  &
   poiseuille_test_name  => test_name, &
   poiseuille_test_description => test_description,&
   poiseuille_coeff_visc => coeff_visc,&
   poiseuille_coeff_f    => coeff_f,   &
   poiseuille_coeff_dir  => coeff_dir, &
   poiseuille_coeff_u    => coeff_u,   &
   poiseuille_coeff_gradu=> coeff_gradu,&
   poiseuille_coeff_p    => coeff_p,   &
   poiseuille_coeff_gradp=> coeff_gradp

 use mod_boxflow_test, only: &
   mod_boxflow_test_constructor, &
   mod_boxflow_test_destructor,  &
   boxflow_test_name  => test_name, &
   boxflow_test_description => test_description,&
   boxflow_coeff_visc => coeff_visc,&
   boxflow_coeff_f    => coeff_f,   &
   boxflow_coeff_dir  => coeff_dir, &
   boxflow_coeff_u    => coeff_u,   &
   boxflow_coeff_gradu=> coeff_gradu,&
   boxflow_coeff_p    => coeff_p,   &
   boxflow_coeff_gradp=> coeff_gradp

!-----------------------------------------------------------------------
 
 implicit none

!-----------------------------------------------------------------------

! Module interface

 public :: &
   mod_testcases_constructor, &
   mod_testcases_destructor,  &
   mod_testcases_initialized, &
   test_name,   & ! name of the test case
   test_description,& ! short description of the test case
   coeff_visc,  & ! viscosity coefficient
   coeff_f,     & ! rhs
   coeff_dir,   & ! Dirichlet datum
   coeff_neu,   & ! Neumann datum
   coeff_rob,   & ! Robin coefficient
   coeff_u,     & ! velocity
   coeff_gradu, & ! velocity gradient
   coeff_p,     & ! pressure
   coeff_gradp, & ! pressure gradient
   coeff_init     ! initial state for time dependent problems

 private

!-----------------------------------------------------------------------

 ! Module types and parameters
 abstract interface
  pure function i_coeff_visc(x) result(mu)
   import :: wp
   real(wp), intent(in) :: x(:,:)
   real(wp) :: mu(size(x,2))
  end function i_coeff_visc
 end interface
 abstract interface
  pure function i_coeff_f(x) result(f)
   import :: wp
   real(wp), intent(in) :: x(:,:)
   real(wp) :: f(size(x,1),size(x,2))
  end function i_coeff_f
 end interface
 abstract interface
  pure function i_coeff_dir(x,breg) result(d)
   import :: wp
   real(wp), intent(in) :: x(:,:)
   integer, intent(in) :: breg
   real(wp) :: d(size(x,1),size(x,2))
  end function i_coeff_dir
 end interface
 abstract interface
  pure function i_coeff_neu(x,breg) result(h)
   import :: wp
   real(wp), intent(in) :: x(:,:)
   integer, intent(in) :: breg
   real(wp) :: h(size(x,1),size(x,2))
  end function i_coeff_neu
 end interface
 abstract interface
  pure function i_coeff_rob(x,breg) result(g)
   import :: wp
   real(wp), intent(in) :: x(:,:)
   integer, intent(in) :: breg
   real(wp) :: g(size(x,2))
  end function i_coeff_rob
 end interface
 abstract interface
  pure function i_coeff_u(x) result(u)
   import :: wp
   real(wp), intent(in) :: x(:,:)
   real(wp) :: u(size(x,1),size(x,2))
  end function i_coeff_u
 end interface
 abstract interface
  pure function i_coeff_gradu(x) result(gu)
   import :: wp
   real(wp), intent(in) :: x(:,:)
   real(wp) :: gu(size(x,1),size(x,1),size(x,2))
  end function i_coeff_gradu
 end interface
 abstract interface
  pure function i_coeff_p(x) result(p)
   import :: wp
   real(wp), intent(in) :: x(:,:)
   real(wp) :: p(size(x,2))
  end function i_coeff_p
 end interface
 abstract interface
  pure function i_coeff_gradp(x) result(gp)
   import :: wp
   real(wp), intent(in) :: x(:,:)
   real(wp) :: gp(size(x,1),size(x,2))
  end function i_coeff_gradp
 end interface
 abstract interface
  pure function i_coeff_init(x) result(u)
   import :: wp
   real(wp), intent(in) :: x(:,:)
   real(wp) :: u(size(x,1),size(x,2))
  end function i_coeff_init
 end interface

 ! Module variables
 ! public members
 character(len=10000), protected ::  &
   test_name
 character(len=10000), allocatable, protected :: &
   test_description(:)
 procedure(i_coeff_visc ), pointer :: coeff_visc  => null()
 procedure(i_coeff_f    ), pointer :: coeff_f     => null()
 procedure(i_coeff_dir  ), pointer :: coeff_dir   => null()
 procedure(i_coeff_neu  ), pointer :: coeff_neu   => null()
 procedure(i_coeff_rob  ), pointer :: coeff_rob   => null()
 procedure(i_coeff_u    ), pointer :: coeff_u     => null()
 procedure(i_coeff_gradu), pointer :: coeff_gradu => null()
 procedure(i_coeff_p    ), pointer :: coeff_p     => null()
 procedure(i_coeff_gradp), pointer :: coeff_gradp => null()
 procedure(i_coeff_init ), pointer :: coeff_init  => null()
 ! compiler bug: both ifort and gfortran refuse the following code
 !protected :: coeff_visc, ...

 logical, protected ::               &
   mod_testcases_initialized = .false.
 ! private members
 character(len=*), parameter :: &
   this_mod_name = 'mod_testcases'

!-----------------------------------------------------------------------

contains

!-----------------------------------------------------------------------

 subroutine mod_testcases_constructor(testname,d)
  character(len=*), intent(in) :: testname
  integer, intent(in) :: d

  integer :: i
  character(len=10000) :: message
  character(len=*), parameter :: &
    this_sub_name = 'constructor'

   !Consistency checks ---------------------------
   if( (mod_messages_initialized.eqv..false.) .or. &
       (mod_kinds_initialized.eqv..false.) ) then
     call error(this_sub_name,this_mod_name, &
                'Not all the required modules are initialized.')
   endif
   if(mod_testcases_initialized.eqv..true.) then
     call warning(this_sub_name,this_mod_name, &
                  'Module is already initialized.')
   endif
   !----------------------------------------------
   
   casename: select case(testname)

    case(cavity_test_name)
     call mod_cavity_test_constructor(d)
     test_name = cavity_test_name
     allocate(test_description(size(cavity_test_description)))
     do i=1,size(test_description)
       test_description(i) = trim(cavity_test_description(i))
     enddo
     coeff_visc  => cavity_coeff_visc
     coeff_f     => cavity_coeff_f
     coeff_dir   => cavity_coeff_dir
     coeff_init  => cavity_coeff_init

    case(cylinder_test_name)
     call mod_cylinder_test_constructor(d)
     test_name = cylinder_test_name
     allocate(test_description(size(cylinder_test_description)))
     do i=1,size(test_description)
       test_description(i) = trim(cylinder_test_description(i))
     enddo
     coeff_visc  => cylinder_coeff_visc
     coeff_f     => cylinder_coeff_f
     coeff_dir   => cylinder_coeff_dir
     coeff_init  => cylinder_coeff_init

    case(squeeze_test_name)
     call mod_squeeze_test_constructor(d)
     test_name = squeeze_test_name
     allocate(test_description(size(squeeze_test_description)))
     do i=1,size(test_description)
       test_description(i) = trim(squeeze_test_description(i))
     enddo
     coeff_visc  => squeeze_coeff_visc
     coeff_f     => squeeze_coeff_f
     coeff_dir   => squeeze_coeff_dir
     coeff_u     => squeeze_coeff_u
     coeff_gradu => squeeze_coeff_gradu
     coeff_p     => squeeze_coeff_p
     coeff_gradp => squeeze_coeff_gradp

    case(poiseuille_test_name)
     call mod_poiseuille_test_constructor(d)
     test_name = poiseuille_test_name
     allocate(test_description(size(poiseuille_test_description)))
     do i=1,size(test_description)
       test_description(i) = trim(poiseuille_test_description(i))
     enddo
     coeff_visc  => poiseuille_coeff_visc
     coeff_f     => poiseuille_coeff_f
     coeff_dir   => poiseuille_coeff_dir
     coeff_u     => poiseuille_coeff_u
     coeff_gradu => poiseuille_coeff_gradu
     coeff_p     => poiseuille_coeff_p
     coeff_gradp => poiseuille_coeff_gradp

    case(boxflow_test_name)
     call mod_boxflow_test_constructor(d)
     test_name = boxflow_test_name
     allocate(test_description(size(boxflow_test_description)))
     do i=1,size(test_description)
       test_description(i) = trim(boxflow_test_description(i))
     enddo
     coeff_visc  => boxflow_coeff_visc
     coeff_f     => boxflow_coeff_f
     coeff_dir   => boxflow_coeff_dir
     coeff_u     => boxflow_coeff_u
     coeff_gradu => boxflow_coeff_gradu
     coeff_p     => boxflow_coeff_p
     coeff_gradp => boxflow_coeff_gradp

    case default
     write(message,'(a,a,a)') 'Unknown test case "',trim(testname),'"'
     call error(this_sub_name,this_mod_name,message)
   end select casename

   write(message,'(a,a,a)') 'Test case: "',trim(test_name),'"'
   call info(this_sub_name,this_mod_name,message)

   mod_testcases_initialized = .true.
 end subroutine mod_testcases_constructor

!-----------------------------------------------------------------------
 
 subroutine mod_testcases_destructor()
  character(len=*), parameter :: &
    this_sub_name = 'destructor'
   
   !Consistency checks ---------------------------
   if(mod_testcases_initialized.eqv..false.) then
     call error(this_sub_name,this_mod_name, &
                'This module is not initialized.')
   endif
   !----------------------------------------------

   casename: select case(trim(test_name))
    case(cavity_test_name)
     call mod_cavity_test_destructor()
    case(cylinder_test_name)
     call mod_cylinder_test_destructor()
    case(squeeze_test_name)
     call mod_squeeze_test_destructor()
    case(poiseuille_test_name)
     call mod_poiseuille_test_destructor()
    case(boxflow_test_name)
     call mod_boxflow_test_destructor()
   end select casename

   ! nullify here the procedure pointers
   nullify( coeff_visc,coeff_f,coeff_dir,coeff_visc,coeff_rob,coeff_u, &
     coeff_gradu,coeff_p,coeff_gradp,coeff_init )

   deallocate(test_description)

   mod_testcases_initialized = .false.
 end subroutine mod_testcases_destructor

!-----------------------------------------------------------------------

end module mod_testcases

